--[[

Copyright (c) 2011-2014 chukong-inc.com

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

]]

--------------------------------
-- @module debug

--[[--

提供调试接口

]]

if ngx and ngx.log then
    -- 如果运行在
    print = function(...)
        local arg = {...}
        for k,v in pairs(arg) do
            arg[k] = tostring(v)
        end
        ngx.log(ngx.ERR, table.concat(arg, "\t"))
    end
end

--[[--

定义一个作废的接口

]]
function DEPRECATED(newfunction, oldname, newname)
    return function(...)
        PRINT_DEPRECATED(string.format("%s() is deprecated, please use %s()", oldname, newname))
        return newfunction(...)
    end
end

--[[--

显示作废信息

]]
function PRINT_DEPRECATED(msg)
    if not DISABLE_DEPRECATED_WARNING then
        printf("[DEPRECATED] %s", msg)
    end
end

--[[--

打印调试信息

### 用法示例

~~~ lua

printLog("WARN", "Network connection lost at %d", os.time())

~~~

@param string tag 调试信息的 tag
@param string fmt 调试信息格式
@param [mixed ...] 更多参数

]]
function printLog(tag, fmt, ...)
    local t = {
        "[",
        string.upper(tostring(tag)),
        "] ",
        string.format(tostring(fmt), ...)
    }
    print(table.concat(t))
end

--[[--

输出 tag 为 ERR 的调试信息

@param string fmt 调试信息格式
@param [mixed ...] 更多参数

]]
function printError(fmt, ...)
    printLog("ERR", fmt, ...)
    print(debug.traceback("", 2))
end

--[[--

输出 tag 为 INFO 的调试信息

@param string fmt 调试信息格式
@param [mixed ...] 更多参数

]]
function printInfo(fmt, ...)
    printLog("INFO", fmt, ...)
end

--[[--

输出值的内容

### 用法示例

~~~ lua

local t = {comp = "chukong", engine = "quick"}

dump(t)

~~~

@param mixed value 要输出的值

@param [string desciption] 输出内容前的文字描述

@parma [integer nesting] 输出时的嵌套层级，默认为 3

]]
function dump(value, desciption, nesting)
    if type(nesting) ~= "number" then nesting = 3 end

    local lookupTable = {}
    local result = {}
    local spc

    local function _v(v)
        if type(v) == "string" then
            v = "\"" .. v .. "\""
        end
        return tostring(v)
    end

    local traceback = string.split(debug.traceback("", 2), "\n")
    print("dump from: " .. string.trim(traceback[3]))

    local function _dump(value, desciption, indent, nest, keylen)
        desciption = desciption or "<var>"
        spc = ""
        if type(keylen) == "number" then
            spc = string.rep(" ", keylen - string.len(_v(desciption)))
        end
        if type(value) ~= "table" then
            result[#result +1 ] = string.format("%s%s%s = %s", indent, _v(desciption), spc, _v(value))
        elseif lookupTable[value] then
            result[#result +1 ] = string.format("%s%s%s = *REF*", indent, desciption, spc)
        else
            lookupTable[value] = true
            if nest > nesting then
                result[#result +1 ] = string.format("%s%s = *MAX NESTING*", indent, desciption)
            else
                result[#result +1 ] = string.format("%s%s = {", indent, _v(desciption))
                local indent2 = indent.."    "
                local keys = {}
                local keylen = 0
                local values = {}
                for k, v in pairs(value) do
                    keys[#keys + 1] = k
                    local vk = _v(k)
                    local vkl = string.len(vk)
                    if vkl > keylen then keylen = vkl end
                    values[k] = v
                end
                table.sort(keys, function(a, b)
                    if type(a) == "number" and type(b) == "number" then
                        return a < b
                    else
                        return tostring(a) < tostring(b)
                    end
                end)
                for i, k in ipairs(keys) do
                    _dump(values[k], k, indent2, nest + 1, keylen)
                end
                result[#result +1] = string.format("%s}", indent)
            end
        end
    end
    _dump(value, desciption, "- ", 1)

    for i, line in ipairs(result) do
        print(line)
    end
end

function release_dump(value, desciption, nesting)
    if type(nesting) ~= "number" then nesting = 3 end

    local lookupTable = {}
    local result = {}
    local spc

    local function _v(v)
        if type(v) == "string" then
            v = "\"" .. v .. "\""
        end
        return tostring(v)
    end

    local traceback = string.split(debug.traceback("", 2), "\n")
    release_print("dump from: " .. string.trim(traceback[3]))

    local function _dump(value, desciption, indent, nest, keylen)
        desciption = desciption or "<var>"
        spc = ""
        if type(keylen) == "number" then
            spc = string.rep(" ", keylen - string.len(_v(desciption)))
        end
        if type(value) ~= "table" then
            result[#result +1 ] = string.format("%s%s%s = %s", indent, _v(desciption), spc, _v(value))
        elseif lookupTable[value] then
            result[#result +1 ] = string.format("%s%s%s = *REF*", indent, desciption, spc)
        else
            lookupTable[value] = true
            if nest > nesting then
                result[#result +1 ] = string.format("%s%s = *MAX NESTING*", indent, desciption)
            else
                result[#result +1 ] = string.format("%s%s = {", indent, _v(desciption))
                local indent2 = indent.."    "
                local keys = {}
                local keylen = 0
                local values = {}
                for k, v in pairs(value) do
                    keys[#keys + 1] = k
                    local vk = _v(k)
                    local vkl = string.len(vk)
                    if vkl > keylen then keylen = vkl end
                    values[k] = v
                end
                table.sort(keys, function(a, b)
                    if type(a) == "number" and type(b) == "number" then
                        return a < b
                    else
                        return tostring(a) < tostring(b)
                    end
                end)
                for i, k in ipairs(keys) do
                    _dump(values[k], k, indent2, nest + 1, keylen)
                end
                result[#result +1] = string.format("%s}", indent)
            end
        end
    end
    _dump(value, desciption, "- ", 1)

    for i, line in ipairs(result) do
        release_print(line)
    end
end

function dumpNodeChildren(node, desc)
    if not node then return end

    local function _dumpNode(node, tab)
        local children = node:getChildren()
        local name, childCnt
        for _,child in ipairs(children) do
            name = child:getName()
            childCnt = child:getChildrenCount()
            if name then
                if childCnt > 0 then
                    tab[name] = {}
                    _dumpNode(child, tab[name])
                else
                    tab[name] = name
                end
            end
        end
    end

    local names = {}
    _dumpNode(node, names)

    dump(names, desc or node:getName(), 100)
end

local STP = require "StackTracePlus"
local profileCounts = {}
local function countHook ()
  local stack = {}
  local depth = 2
  local caller = debug.getinfo(depth, "Snl")
  while caller do
    local name,source,line =  STP.getFuncInfo(caller)
    caller.s = name.."@"..source..":"..line
    table.insert(stack, 1, caller.s)
    depth = depth + 1
    caller = debug.getinfo(depth)
  end

  local trace = table.concat(stack, ";")
  -- if trace == "" then
  --   return
  -- end
  if profileCounts[trace] == nil then
    profileCounts[trace] = 1
  else
    profileCounts[trace] = profileCounts[trace] + 1
  end
end

function startProfile(hookCount)
  debug.sethook(countHook, "", hookCount)
end

function reportProfile(filename)
  local outfile = io.open(filename, "w")
  for k,v in pairs(profileCounts) do
    outfile:write(k .. " " .. v .. "\n")
  end
  outfile:close()
end

function stopProfile ()
  debug.sethook()
end

function clearProfile ()
  profileCounts = {}
end

local ProFi = require 'quick.ProFi'
local ProFiType = 0
local DefaultHoodCount = 1000
function debug.profiStart(type)
    ProFiType = type or 0
    if ProFiType == 1 then
        startProfile(DefaultHoodCount)
    else
        ProFi:start("Lua")
    end
    release_print("debug.profiStart",ProFiType)
end

function debug.profiYeild()
    if ProFiType == 1 then

    else
        ProFi:stopHooks()
    end
end

function debug.profiResume()
    if ProFiType == 1 then

    else
        ProFi:startHooks()
    end
end

function debug.profiStop()
    local filename =  nil
    if cc.PLATFORM_OS_ANDROID == cc.Application:getInstance():getTargetPlatform() then
        filename = cc.getExternalPath().."/ProfilingReport.txt"
    else
        filename = cc.FileUtils:getInstance():getWritablePath()..'ProfilingReport.txt'
    end
    if ProFiType == 1 then
        stopProfile()
        reportProfile(filename)
    else
        ProFi:stop()
        ProFi:writeReport(filename)
    end
    release_print("debug.profiStop",filename)
end


local string_find = string.find
local string_format = string.format

-- 对象监控器
local _monitor = nil
-- 把对象添加到监控器，在应该被回收之后还存在的话，说明对象泄露了
if MONITOR_LEAK then
    function debug.addMonitor(obj, key)
        if not obj then return end
        if not _monitor then
            _monitor = {}
            setmetatable(_monitor, {__mode = "kv"})
        end
        -- print("debug:addToMonitor", obj, key)
        _monitor[obj] = key
    end

    function debug.dumpMonitor(key, sortType)
        local ret = {}
        for k,v in pairs(_monitor or {}) do
            if ret[v] then
                ret[v] = ret[v] + 1
            else
                ret[v] = 1
            end
        end
        if key then
            local findRet = {}
            for k,v in pairs(ret) do
                if string_find(k, key) then
                    findRet[k] = v
                end
            end
            ret = findRet
        end

        local list = {}
        for k,v in pairs(ret) do
            list[#list + 1] = {k=k, v=v}
        end

        local sortFunction = function(a, b)
            return a.v > b.v
        end
        if sortType == "sortbykey" then
            sortFunction = function(a, b)
                return a.k < b.k
            end
        end

        table.sort(list, sortFunction)

        print("debug.dumpMonitor start")
        local spc
        for i,v in ipairs(list) do
            spc = string.rep(" ", 20 - #(v.k))
            print(string_format("    \"%s\"%s = %s", v.k, spc, v.v))
        end
        print("debug.dumpMonitor end")
        return ret
    end
else
    function debug.addMonitor(obj, key)
    end
    function debug.dumpMonitor()
    end
end

local MemLeak = require("app.utils.memleak")

function debug.startMemCache()
    MemLeak:init(app:getStorePath().."/memleak"..tostring(math.floor(app:getTime()))..".log")
    collectgarbage("collect")
    collectgarbage("collect")
    collectgarbage("collect")
    MemLeak:cacheMemory()
end

function debug.diffMemCache()
    collectgarbage("collect")
    collectgarbage("collect")
    collectgarbage("collect")
    MemLeak:cacheMemory()
    MemLeak:differCache()
end

local Socket = require "socket"
local socketGettime = Socket.gettime
local lsnapshot = require("app.public.luatraverse.snapshot")
local _shotDict = {}

-- tag 快照的tag
-- tag ~= nil 第一次调用tag，保存t快照，第二次调用tag，输出两次差异到文件
-- tag == nil 立刻保存一次当前快照到文件
function debug.luaSnapshot(tag)
    print("debug.luaSnapshot tag", tag)

    local monitorInfo = debug.dumpMonitor()

    local diffs
    local date = ""
    if tag then
        local oldShot = _shotDict[tag]
        if oldShot then
            _shotDict[tag] = nil
            collectgarbage("collect")
            collectgarbage("collect")
            collectgarbage("collect")
            local newShot = lsnapshot.snapshot()

            -- dump(oldShot, "oldShot")
            -- dump(newShot, "newShot")

            diffs = {}
            date = ""
            for k,v in pairs(newShot) do
                if oldShot[k] == nil then
                    diffs[#diffs + 1] = v
                else
                    local old = oldShot[k]
                    local isChange = false
                    if old.child ~= v.child then
                        isChange = true
                        v.childChange = v.child - old.child
                    end

                    local refs = {}
                    for obj, ref in pairs(old.refs) do
                        if v.refs[obj] == nil then
                            isChange = true
                            refs[obj] = "---  " .. ref
                        end
                    end
                    for obj, ref in pairs(v.refs) do
                        if old.refs[obj] == nil then
                            isChange = true
                            refs[obj] = "+++  " .. ref
                        end
                    end
                    v.refs = refs

                    if isChange then
                        diffs[#diffs + 1] = v
                    end
                end
            end

        else
            collectgarbage("collect")
            collectgarbage("collect")
            collectgarbage("collect")
            _shotDict[tag] = lsnapshot.snapshot()
        end
    else
        collectgarbage("collect")
        collectgarbage("collect")
        collectgarbage("collect")
        local newShot = lsnapshot.snapshot()
        diffs = {}
        for k,v in pairs(newShot) do
            diffs[#diffs + 1] = v
        end
        date = os.date("%Y-%m-%d-%H:%M:%S", math.ceil(socketGettime()))
    end

    if diffs then
        -- 按引用和child排序
        table.sort(diffs, function(a, b)
            local num_a = a.refnum + a.child
            local num_b = b.refnum + b.child
            return num_a > num_b
        end)

        print("luaSnapshot diff start tag", tag)

        -- 创建保存内容
        local fileName = app:getStorePath().."/lshot_%s_%s.txt"
        fileName = string.format(fileName, tag or "", date)
        print("luaSnapshot fileName", fileName)
        print("luaSnapshot #diffs", #diffs)

        local file = io.open(fileName, "w")

        file:write(string_format("memory = %i M\n", collectgarbage"count" / 1024))
        file:write(string_format("different num = %i\n\n", #diffs))

        for k,v in pairs(monitorInfo) do
            file:write(string_format("%10s %3i\n\n", tostring(k), v))
        end

        for _,data in ipairs(diffs) do
            file:write(string_format("%3i %3i = %s \n", data.child, data.refnum, data.name))
            if data.type ~= "[unknown]" then
                file:write(string_format("    %s = %s\n", "type", data.type))
            end
            if data.path then
                file:write(string_format("    %s = %s\n", "path", data.path))
            end
            if data.childChange then
                file:write(string_format("    %s = %d\n", "childChange", data.childChange))
            end
            for k,v in pairs(data.refs) do
                file:write(string_format("    %s = %s\n", v, tostring(k)))
            end
            file:write("\n")
        end

        file:close()

        print("luaSnapshot diff end tag", tag)
    end
end
